package com.stars.easyms.datasource;

import com.stars.easyms.base.asynchronous.BaseAsynchronousTask;
import com.stars.easyms.datasource.holder.EasyMsSqlExecuteContentHolder;
import com.stars.easyms.datasource.holder.EasyMsSynchronizationManager;
import com.stars.easyms.datasource.util.ParameterUtil;
import com.stars.easyms.base.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.BoundSql;
import org.springframework.util.Assert;

import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

/**
 * <p>className: EasyMsDataSourceMonitor</p>
 * <p>description: EasyMs数据源监控器</p>
 *
 * @author guoguifang
 * @version 1.2.1
 * @date 2019-05-23 18:03
 */
@Slf4j
public final class EasyMsDataSourceMonitor extends BaseAsynchronousTask<Map<EasyMsDataSource, SqlExecuteContent>> {

    private static final AtomicLong EXECUTE_SEQ = new AtomicLong();

    private static final int DEFAULT_SLOW_SQL_EXECUTION_TIME = 1000;

    /**
     * 数据源正在使用次数加1
     */
    public void start(String sqlId, BoundSql boundSql) {
        SqlExecuteContent sqlExecuteContent = EasyMsSqlExecuteContentHolder.getCurrentSqlExecuteContent();
        if (sqlExecuteContent != null) {
            sqlExecuteContent.setSqlId(sqlId);
            sqlExecuteContent.setBoundSql(boundSql);
        } else {
            sqlExecuteContent = new SqlExecuteContent(EXECUTE_SEQ.incrementAndGet(), sqlId, boundSql);
            EasyMsDataSource easyMsDataSource = EasyMsSynchronizationManager.getEasyMsDataSource();
            Assert.notNull(easyMsDataSource, "Current EasyMsDataSource instance is null!");
            easyMsDataSource.increase(sqlExecuteContent);
            EasyMsSqlExecuteContentHolder.setCurrentSqlExecuteContent(sqlExecuteContent);
        }
        sqlExecuteContent.executeStart();
    }

    /**
     * 数据源正在使用次数减1，如果一个SQL用时超过500毫秒则认为是慢SQL需要记录下来以备分析
     */
    public void end() {
        SqlExecuteContent sqlExecuteContent = EasyMsSqlExecuteContentHolder.getCurrentSqlExecuteContent();
        if (sqlExecuteContent != null) {
            EasyMsDataSource easyMsDataSource = EasyMsSynchronizationManager.getEasyMsDataSource();
            Assert.notNull(easyMsDataSource, "Current EasyMsDataSource instance is null!");
            easyMsDataSource.decrease(sqlExecuteContent.getSeq());
            sqlExecuteContent.executeEnd();
            if (sqlExecuteContent.getSqlException() != null || sqlExecuteContent.getUsedTime() >= DEFAULT_SLOW_SQL_EXECUTION_TIME) {
                add(Collections.singletonMap(easyMsDataSource, sqlExecuteContent));
            }
            EasyMsSqlExecuteContentHolder.clear();
        }
    }

    public void sqlException(SQLException sqlException) {
        SqlExecuteContent sqlExecuteContent = EasyMsSqlExecuteContentHolder.getCurrentSqlExecuteContent();
        if (sqlExecuteContent != null) {
            sqlExecuteContent.setSqlException(sqlException);
        }
    }

    @Override
    protected boolean execute(Map<EasyMsDataSource, SqlExecuteContent> easyMsDataSourceSqlExecuteContentMap) {
        easyMsDataSourceSqlExecuteContentMap.forEach((easyMsDataSource, sqlExecuteContent) -> {
            // 获取sql执行消息
            String message = MessageFormatUtil.format("### Execution start time: {}, execution end time: {}, execution used time: {}." +
                            "\r\n### Data source: {}\r\n### SQL ID: {}\r\n### SQL: {}\r\n### Parameter: {}",
                    DateTimeUtil.getDatetimeNormalStrWithMills(sqlExecuteContent.getStartTime()),
                    DateTimeUtil.getDatetimeNormalStrWithMills(sqlExecuteContent.getEndTime()),
                    TimeUtil.formatTime(sqlExecuteContent.getUsedTime(), TimeUtil.ENGLISH, TimeUtil.MILLISECOND),
                    easyMsDataSource.getAbbrUrl(), sqlExecuteContent.getSqlId(),
                    StringFormatUtil.formatBlank(sqlExecuteContent.getBoundSql().getSql()),
                    ParameterUtil.getParameterString(sqlExecuteContent.getBoundSql().getParameterObject()));

            // 记录相关日志
            if (sqlExecuteContent.getSqlException() != null) {
                log.error("Execute sql fail!\r\n" + message + "\r\n### Caused by: " + ExceptionUtil.getThowableTrace(sqlExecuteContent.getSqlException()));
            } else {
                log.warn("SQL execution time is too long, please note!\r\n" + message);
            }
        });
        return true;
    }

    public static EasyMsDataSourceMonitor getInstance() {
        return InstanceHolder.INSTANCE;
    }

    private static class InstanceHolder {
        private static final EasyMsDataSourceMonitor INSTANCE = new EasyMsDataSourceMonitor();
    }

    private EasyMsDataSourceMonitor() {
    }
}